package nancal.mp.service.sms

import nancal.mp.db.mongo.MobileCodeModuleEnum
import nancal.mp.db.mongo.entity.log.MobileCodeLog
import nancal.mp.db.mongo.mor
import nancal.mp.db.redis.rer
import nbcp.base.comm.ApiResult
import nbcp.base.extend.*
import nbcp.base.model.HttpInvoke
import nbcp.myoql.db.mongo.query
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.context.annotation.Primary
import org.springframework.stereotype.Service
import java.io.UnsupportedEncodingException
import java.net.URLEncoder
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import java.text.SimpleDateFormat
import java.time.LocalDateTime
import java.util.*
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
import kotlin.Throws

/**
 * @ClassName HuaweiSmsService
 * @author xuebin
 * @version 1.0.0
 * @Description 华为云短信服务
 * @createTime 2021年12月11日 15:47:00
 */
@Primary
@Service
@ConditionalOnProperty("app.sms.huaweiyun.url")
class HuaweiSmsService : SmsStrategy {

    companion object {
        private val logger: Logger = LoggerFactory.getLogger(this::class.java)
    }

    //无需修改,用于格式化鉴权头域,给"X-WSSE"参数赋值
    private val WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\""

    //无需修改,用于格式化鉴权头域,给"Authorization"参数赋值
    private val AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\""

    //必填,请参考"开发准备"获取如下数据,替换为实际值
    //APP接入地址(在控制台"应用管理"页面获取)+接口访问URI
    @Value("\${app.sms.huaweiyun.url:}")
    var url: String = ""

    @Value("\${app.sms.huaweiyun.appKey}")
    var appKey: String = ""

    @Value("\${app.sms.huaweiyun.appSecret}")
    var appSecret: String = ""

    data class SmsDb(
            var channel: String = "",
            var signature: String,
            var templates: Map<MobileCodeModuleEnum, String> = mapOf()
    )


    /**
     * 19b62e3d8fe54b299e49fe88d9a2626c: 您的验证码是 ${1},请于5分钟内完成验证,若非本人操作,请忽略本短信。
     * d5bd8c8ccf3342f484cb1432cd3d6ec8: 您的的初始登录密码为：${1}，为了您的账户安全，请勿将密码告知他人,登录后请尽快修改密码。
     * ed1f57902b8c41e3a7cd240d40ec325f: 您正在[重置/找回密码]，验证码${1} ，有效时长5分钟。不要转给他人，若非本人操作，请修改密码。
     */

    private val nengke_cn_db = SmsDb(
            "8823053036202",
//        "8821120734444",
//        "验证码",
            "能科瑞元",
            mapOf(
//            MobileCodeModuleEnum.Login to "30229d1df1b2447082b99c3f861378a2",
                    MobileCodeModuleEnum.Login to "19b62e3d8fe54b299e49fe88d9a2626c",
                    MobileCodeModuleEnum.Registe to "19b62e3d8fe54b299e49fe88d9a2626c",
                    MobileCodeModuleEnum.ForgetPassword to "a3e7248f739e410bbdf535926d4c0946",
                    MobileCodeModuleEnum.ChangeMobile to "19b62e3d8fe54b299e49fe88d9a2626c",
                    MobileCodeModuleEnum.SendPassword to "d5bd8c8ccf3342f484cb1432cd3d6ec8",
            )
    )

    /**
     * cccfd9d02b054e8ba13ac395ba96b40a: Your verification code is:${1}. Please finish verification within 5 minutes. Please disregard the text if the operation was not initiated by you.
     * f483565d188a4d7bb203e0010cffe11a: Your initial login password is: ${1}. For your account security, please change your password as soon as possible.
     * f666dd1fa67e41af9a8e4ba14de7e9fb:You are resetting or retrieving your password. The verification code ${1} is valid for 5 minutes. If you do not operate by yourself,please change the password.
     */
    private val nengke_en_db = SmsDb(
            "8821120734444",
//        "8823053036202",
            "能科瑞元", mapOf(
            MobileCodeModuleEnum.Login to "cccfd9d02b054e8ba13ac395ba96b40a",
            MobileCodeModuleEnum.Registe to "cccfd9d02b054e8ba13ac395ba96b40a",
            MobileCodeModuleEnum.ForgetPassword to "f666dd1fa67e41af9a8e4ba14de7e9fb",
            MobileCodeModuleEnum.ChangeMobile to "cccfd9d02b054e8ba13ac395ba96b40a",
            MobileCodeModuleEnum.SendPassword to "f483565d188a4d7bb203e0010cffe11a",
    )
    )

    private val zao_db = SmsDb(
            "8823060208528", "后厂造", mapOf(
            MobileCodeModuleEnum.Login to "64700161f2794718a502d5ca170a1e1e",
            MobileCodeModuleEnum.Registe to "64700161f2794718a502d5ca170a1e1e",
            MobileCodeModuleEnum.ForgetPassword to "64700161f2794718a502d5ca170a1e1e",
            MobileCodeModuleEnum.ChangeMobile to "64700161f2794718a502d5ca170a1e1e",
            MobileCodeModuleEnum.SendPassword to "5e91424bca4b4eb3b892e6833e72393e"
    )
    )

    private val notification_db = SmsDb(
            "8823053111786",
            "能科瑞元", mapOf(
            MobileCodeModuleEnum.PasswordWillExpire to "b19d16e457ec406a94b6cdb3b7027f90",
            MobileCodeModuleEnum.PasswordExpired to "3b88f9f574d446558ea3e137e22c304b",
    )
    )

    private val notification_en_db = SmsDb(
            "8823053111786",
            "能科瑞元", mapOf(
            MobileCodeModuleEnum.PasswordWillExpire to "509dc9a6deb745ff874fd1c01180e9c0",
            MobileCodeModuleEnum.PasswordExpired to "160714d7c9a74cdb9d2d5c6208f56643",
    )
    )


    private fun getTemplateSetting(
            productLineCode: String,
            lang: String = ""
    ): SmsDb {
        if (productLineCode == "zao") {
            return zao_db
        }

        if (productLineCode == "notification") {
            if (lang == "en") {
                return notification_en_db
            }
            return notification_db
        }

        if (lang == "en") {
            return nengke_en_db
        }
        return nengke_cn_db
    }


    override fun getSmsCode(
            productLineCode: String,
            module: MobileCodeModuleEnum,
            recvMobile: String
    ): ApiResult<String> {
        return ApiResult.of(
                rer.sys.smsCode(
                        listOf(
                                productLineCode,
                                module.toString(),
                                recvMobile
                        ).filter { it.HasValue }.joinToString("-")
                ).get()
        );
    }

    /**
     * 发送短信
     * @param receiver 短信接收手机号,必填,全局号码格式(包含国家码),示例:+8615123456789,多个号码之间用英文逗号分隔
     * @param templateParas 短信模板参数
     * @param statusCallBack 选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告
     * @return String 返回内容
     * @throws Exception e
     */
    @Throws(Exception::class)
    override fun sendSmsCode(
            productLineCode: String,
            module: MobileCodeModuleEnum,
            recvMobile: String,
            code: String,
            lang: String
    ): ApiResult<String> {

        //规则
        /*val msg = check(module, recvMobile)
        if (msg.HasValue) {
            return ApiResult.error(msg,500);
        }*/

        /**
         * 选填,使用无变量模板时请赋空值 String templateParas = "";
         * 单变量模板示例:模板内容为"您的验证码是${1}"时,templateParas可填写为"[\"369751\"]"
         * 双变量模板示例:模板内容为"您有${1}件快递请到${2}领取"时,templateParas可填写为"[\"3\",\"人民公园正门\"]"
         * 模板中的每个变量都必须赋值，且取值不能为空
         * 查看更多模板和变量规范:产品介绍>模板和变量规范
         */
        //String templateParas = "[\"369751\"]"; //模板变量，此处以单变量验证码短信为例，请客户自行生成6位验证码，并定义为字符串类型，以杜绝首位0丢失的问题（例如：002569变成了2569）。
        val templateParas = """["${code}"]"""

        //发送短信
        val sendSms = sendSms(productLineCode, module, recvMobile, lang, templateParas)

        if (sendSms.code == 0) {
            // 验证码存入redis
            rer.sys.smsCode(
                    listOf(
                            productLineCode,
                            module.toString(),
                            recvMobile
                    ).filter { it.HasValue }.joinToString("-")
            ).set(code);
        }

        return sendSms
    }

    /**
     * 发送短信通知
     */
    @Throws(Exception::class)
    override fun sendSmsNotification(
            productLineCode: String,
            module: MobileCodeModuleEnum,
            recvMobile: String,
            lang: String,
            params: List<String>
    ): ApiResult<String> {
        /**
         * 选填,使用无变量模板时请赋空值 String templateParas = "";
         * 单变量模板示例:模板内容为"您的验证码是${1}"时,templateParas可填写为"[\"369751\"]"
         * 双变量模板示例:模板内容为"您有${1}件快递请到${2}领取"时,templateParas可填写为"[\"3\",\"人民公园正门\"]"
         * 模板中的每个变量都必须赋值，且取值不能为空
         * 查看更多模板和变量规范:产品介绍>模板和变量规范
         */

        var templateParas = ""
        if (params.filter { it.HasValue }.toList().any()) {
            templateParas = params.filter { it.HasValue }.toList().ToJson()
            println(templateParas)
        }

        val sendSms = sendSms(productLineCode.AsString("notification"), module, recvMobile, lang, templateParas)
        return sendSms
    }

    fun sendSms(
            productLineCode: String,
            module: MobileCodeModuleEnum,
            recvMobile: String,
            lang: String,
            templateParas: String
    ): ApiResult<String> {

        //选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告
        val statusCallBack = ""
        //请求Body,不携带签名名称时,signature请填null

        var setting = getTemplateSetting(productLineCode, lang);


        var channel = setting.channel
        var signature = setting.signature;
        var templateCode = setting.templates.get(module);
        if (templateCode.isNullOrEmpty()) {
            return ApiResult.error("未定义 ${module} 短信模板")
        }

        val body =
                buildRequestBody(channel, recvMobile, templateCode, templateParas, statusCallBack, signature)
        if (body.isEmpty()) {
            logger.error("body is null.")
            return ApiResult.error("body is null.", 500)
        }

        val smsLog = MobileCodeLog();
        smsLog.module = module
        smsLog.mobile = recvMobile
        smsLog.templateCode = templateCode
        smsLog.param = templateParas

        //请求Headers中的X-WSSE参数值
        val wsseHeader = buildWsseHeader(appKey, appSecret)
        if (wsseHeader.isEmpty()) {
            logger.error("wsse header is null.")
            return ApiResult.error("wsse header is null.", 500)
        }

        trustAllHttpsCertificates()

        val http = HttpInvoke()
        http.url = (url)

        http.requestHeader.put("Content-Type", "application/x-www-form-urlencoded")
        http.requestHeader.put("Authorization", AUTH_HEADER_VALUE)
        http.requestHeader.put("X-WSSE", wsseHeader)

        try {
            if (recvMobile.startsWith("110") == false) {
                http.postString = body
                var res = http.doInvoke();
                if( res.isError){
                    throw RuntimeException(res.status.toString())
                }

                var result =res.responseText!!
                smsLog.result = result;
                val smsResult = result.FromJson(SmsResult::class.java)
                smsLog.result = smsResult

                smsLog.bizId = smsResult!!.result.map { it.smsMsgId }.toString()

                if (smsResult.code != "000000") {
                    logger.error(result)

                    return ApiResult.error(smsResult.description, smsResult.code.AsInt(-1));
                }
            }

            return ApiResult()
        } catch (e: Exception) {
            val err = e.message ?: "发送短信时失败"

            smsLog.errorMessage = err;
            return ApiResult.error(err);
        } finally {
            mor.log.mobileCodeLog.doInsert(smsLog)
        }
    }

    /**
     * 构造请求Body体
     * @param sender
     * @param receiver
     * @param templateId
     * @param templateParas
     * @param statusCallBack
     * @param signature | 签名名称,使用国内短信通用模板时填写
     * @return
     */
    fun buildRequestBody(
            sender: String, receiver: String, templateId: String, templateParas: String,
            statusCallBack: String, signature: String
    ): String {
        if (sender.isEmpty() || receiver.isEmpty() || templateId.isEmpty()
        ) {
            throw RuntimeException("buildRequestBody(): sender, receiver or templateId is null.")
        }
        val map: MutableMap<String, String> = HashMap()
        map["from"] = sender
        map["to"] = receiver
        map["templateId"] = templateId
        if (!templateParas.isEmpty()) {
            map["templateParas"] = templateParas
        }
        if (!statusCallBack.isEmpty()) {
            map["statusCallback"] = statusCallBack
        }
        if (!signature.isEmpty()) {
            map["signature"] = signature
        }
        val sb = StringBuilder()
        var temp: String? = ""
        for (s in map.keys) {
            try {
                temp = URLEncoder.encode(map[s], "UTF-8")
            } catch (e: UnsupportedEncodingException) {
                e.printStackTrace()
            }
            sb.append(s).append("=").append(temp).append("&")
        }
        return sb.toString().Slice(0, -1)
    }

    /**
     * 构造X-WSSE参数值
     * @param appKey
     * @param appSecret
     * @return
     */
    fun buildWsseHeader(appKey: String, appSecret: String): String {
        if (appKey.isEmpty() || appSecret.isEmpty()) {
            throw RuntimeException("buildWsseHeader(): appKey or appSecret is null.")
        }
        val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
        val time = sdf.format(Date()) //Created
        val nonce = UUID.randomUUID().toString().replace("-", "") //Nonce
        val md: MessageDigest
        var passwordDigest: ByteArray? = null
        try {
            md = MessageDigest.getInstance("SHA-256")
            md.update((nonce + time + appSecret).toByteArray())
            passwordDigest = md.digest()
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        }

        //如果JDK版本是1.8,请加载原生Base64类,并使用如下代码
        val passwordDigestBase64Str = Base64.getEncoder().encodeToString(passwordDigest) //PasswordDigest
        //如果JDK版本低于1.8,请加载三方库提供Base64类,并使用如下代码
        //String passwordDigestBase64Str = Base64.encodeBase64String(passwordDigest); //PasswordDigest
        //若passwordDigestBase64Str中包含换行符,请执行如下代码进行修正
        //passwordDigestBase64Str = passwordDigestBase64Str.replaceAll("[\\s*\t\n\r]", "");
        return kotlin.String.format(
                WSSE_HEADER_FORMAT,
                appKey,
                passwordDigestBase64Str,
                nonce,
                time
        )
    }

    /*** @throws Exception
     */
    @Throws(Exception::class)
    fun trustAllHttpsCertificates() {
        val trustAllCerts = arrayOf<TrustManager>(
                object : X509TrustManager {
                    @Throws(CertificateException::class)
                    override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
                        return
                    }

                    @Throws(CertificateException::class)
                    override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
                        return
                    }

                    override fun getAcceptedIssuers(): Array<X509Certificate>? {
                        return null
                    }
                }
        )
        val sc = SSLContext.getInstance("SSL")
        sc.init(null, trustAllCerts, null)
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.socketFactory)
    }

    /**
     * 规则：每个模块业务每小时15条，每自然日45条。
     */
    fun check(module: MobileCodeModuleEnum, recvMobile: String): String {
        var lastHour = LocalDateTime.now().minusHours(1)

        var hourCount = mor.log.mobileCodeLog.query()
                .where { it.module mongoEquals module }
                .where { it.mobile mongoEquals recvMobile }
                .where { it.createAt greaterThan lastHour }
                .count()

        if (hourCount > 25) {
            return "操作过于频繁，请1分钟后重试！"
        }


        var lastDay = LocalDateTime.now().minusDays(1)

        var dayCount = mor.log.mobileCodeLog.query()
                .where { it.module mongoEquals module }
                .where { it.mobile mongoEquals recvMobile }
                .where { it.createAt greaterThan lastDay }
                .count()

        if (dayCount > 45) {
            return "操作过于频繁，请1分钟后重试！"
        }

        return "";
    }

    class SmsResult() {
        val result: List<SmsInfo> = arrayListOf()
        val code = ""
        val description = ""
    }

    class SmsInfo() {
        val originTo = ""
        val createTime = ""
        val from = ""
        val smsMsgId = ""
        val status = ""
    }

}
